/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt.inspections;

import com.intellij.codeInspection.*;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.gwt.GwtBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.IncorrectOperationException;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;

/**
 * @author nik
 */
public class GwtNonSerializableRemoteServiceMethodParametersInspection extends LocalInspectionTool {
  private static final Logger LOG = Logger.getInstance("#com.intellij.gwt.inspections.GwtNonSerializableRemoteServiceMethodParametersInspection");

  @Nullable
  public ProblemDescriptor[] checkClass(@NotNull PsiClass aClass, @NotNull InspectionManager manager, boolean isOnTheFly) {
    if (RemoteServiceUtil.isRemoteServiceInterface(aClass)) {
      return checkRemoteService(aClass, manager);
    }
    return null;
  }

  private static ProblemDescriptor[] checkRemoteService(final PsiClass aClass, final InspectionManager manager) {
    ArrayList<ProblemDescriptor> result = new ArrayList<ProblemDescriptor>(0);

    final PsiManager psiManager = PsiManager.getInstance(manager.getProject());
    GlobalSearchScope scope = aClass.getResolveScope();

    final PsiElementFactory psiElementFactory = psiManager.getElementFactory();
    final PsiClassType isSerialType = psiElementFactory.createType(psiManager.findClass(RemoteServiceUtil.IS_SERIALIZABLE_INTERFACE_NAME, scope));
    for (final PsiMethod method : aClass.getMethods()) {
      for (final PsiParameter param : method.getParameterList().getParameters()) {
        checkTypeSerial(param.getTypeElement(), isSerialType, manager, result);

/*
      if ( isCollection( param.getTypeElement().getType() ) )
      {
        PsiDocComment doc = method.getDocComment();
        if ( doc == null )
          com.intellij.gwt.res.add( manager.createProblemDescriptor(
                  param,
                  "No GWT-required javadoc tag @gwt-typeArgs",
                  new ParamTypeQuickFix(param, psiManager, isSerialType, method),
                  ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
        else
          for ( PsiDocTag tag : doc.getTags() )
            if ( tag.getName().equals("gwt-typeArgs"))
              continue;
      }
*/
      }
      final PsiTypeElement returnTypeElement = method.getReturnTypeElement();
      if (returnTypeElement != null) {
        checkTypeSerial(returnTypeElement, isSerialType, manager, result);
      }
    }

    return result.toArray(new ProblemDescriptor[result.size()]);
  }

  private static void checkTypeSerial(PsiTypeElement typeElement,
                                      final PsiClassType isSerialType,
                                      InspectionManager manager,
                                      ArrayList<ProblemDescriptor> res) {
    PsiType type = typeElement.getType();
    if (!type.isValid() || (!(type instanceof PsiClassType) && !(type instanceof PsiArrayType))) return;

    if (GwtSerializableUtil.isCollection(type)) return;

    if (!GwtSerializableUtil.isSerializable(isSerialType, type)) {
      while (type instanceof PsiArrayType) {
        type = ((PsiArrayType)type).getComponentType();
      }

      if (type instanceof PsiClassType) {
        PsiClass aClass = ((PsiClassType)type).resolve();
        if (aClass != null) {
          final LocalQuickFix quickFix = aClass instanceof PsiCompiledElement ? null : new InheritFromIsSerializableQuickFix(aClass, isSerialType);
          final String description = GwtBundle.message("problem.description.gwt.serializable.type.0.should.implements.marker.interface.isserializable", type.getCanonicalText());
          res.add(manager.createProblemDescriptor(typeElement, description, quickFix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
        }
      }
    }

  }

  @NotNull
  public String getGroupDisplayName() {
    return GwtInspectionsProvider.GWT_INSPECTIONS_GROUP;
  }

  @NotNull
  public HighlightDisplayLevel getDefaultLevel() {
    return HighlightDisplayLevel.ERROR;
  }

  public boolean isEnabledByDefault() {
    return true;
  }

  @NotNull
  public String getDisplayName() {
    return GwtBundle.message("inspection.name.non.serializable.service.method.parameters");
  }

  @NotNull
  @NonNls
  public String getShortName() {
    return "NonSerializableServiceParameters";
  }

  private static class InheritFromIsSerializableQuickFix implements LocalQuickFix {
    private final PsiClassType mySerialType;
    private final PsiClass myClass;

    public InheritFromIsSerializableQuickFix(final PsiClass aClass, final PsiClassType serialType) {
      myClass = aClass;
      mySerialType = serialType;
    }

    @NotNull
    public String getName() {
      return GwtBundle.message("quick.fix.name.inherit.0.from.isserializable", myClass.getName());
    }

    @NotNull public String getFamilyName() {
      return GwtInspectionsProvider.GWT_QUICKFIXES_FAMILY;
    }

    public void applyFix(@NotNull Project project, ProblemDescriptor descriptor) {
      if (ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(myClass.getContainingFile().getVirtualFile()).hasReadonlyFiles()) {
        return;
      }

      try {
        final PsiReferenceList implList = myClass.getImplementsList();
        if (implList == null) return;
        implList.add(PsiManager.getInstance(project).getElementFactory().createReferenceElementByType(mySerialType));
      }
      catch (IncorrectOperationException e) {
        LOG.error(e);
      }
    }
  }

  private static class MyClassFilter implements TreeClassChooser.ClassFilter {
    private final PsiClassType serialType;
    private final Project project;

    public MyClassFilter(PsiClassType serialType, Project project) {
      this.serialType = serialType;
      this.project = project;
    }

    public boolean isAccepted(PsiClass aClass) {
      return GwtSerializableUtil.isSerializable(serialType, PsiManager.getInstance(project).getElementFactory().createType(aClass));
    }
  }

  private class ParamTypeQuickFix implements LocalQuickFix {
    private final PsiParameter myParameter;
    private final PsiManager myPsiManager;
    private final PsiClassType mySerialType;
    private final PsiMethod myMethod;

    public ParamTypeQuickFix(PsiParameter param, PsiManager psiManager, PsiClassType serialType, PsiMethod method) {
      myParameter = param;
      myPsiManager = psiManager;
      mySerialType = serialType;
      myMethod = method;
    }

    @NotNull
    public String getName() {
      return GwtBundle.message("quick.fix.name.insert.gwt.required.javadoc.tags");
    }

    @NotNull
    public String getFamilyName() {
      return GwtInspectionsProvider.GWT_QUICKFIXES_FAMILY;
    }

    public void applyFix(final @NotNull Project project, ProblemDescriptor descriptor) {
      if (ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(myMethod.getContainingFile().getVirtualFile()).hasReadonlyFiles()) {
        return;
      }

      ApplicationManager.getApplication().invokeLater(new Runnable() {
        public void run() {
          TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).createNoInnerClassesScopeChooser(
            GwtBundle.message("chooser.text.param.0.choose.collection.element.class", myParameter.getName()),
            GlobalSearchScope.packageScope(myPsiManager.findPackage("java.lang"), false).uniteWith(project.getProjectScope()),
            new MyClassFilter(mySerialType, project), null);

          chooser.showDialog();
          PsiClass cls = chooser.getSelectedClass();

          if (cls != null) {
            ApplicationManager.getApplication().runWriteAction(new Runnable() {
              public void run() {
                StringBuilder builder = new StringBuilder();
                builder.append("/**\n");
                if (GwtSerializableUtil.isCollection(myMethod.getReturnType())) builder.append(" *  @gwt-typeArgs " + "<...>\n");
                for (PsiParameter param : myMethod.getParameterList().getParameters()) {
                  if (GwtSerializableUtil.isCollection(param.getType())) builder.append(" *  @gwt-typeArgs ").append(param.getName()).append(" <...>\n");
                }
                builder.append("*/\n");

                PsiDocComment doc;
                try {
                  doc = myPsiManager.getElementFactory().createDocCommentFromText(builder.toString(), myMethod);
                  myMethod.getParent().addBefore(doc, myMethod);
                }
                catch (IncorrectOperationException e) {
                }
              }
            });
          }
        }
      });
    }
  }
}
